using System;
using System.Collections.Generic;

namespace InfiniTec.Threading
{
    public sealed class AsyncOperationTracker
    {
        private readonly Dictionary<AsyncOperationIdentifier, AsyncOperation> _PendingOperations = new Dictionary<AsyncOperationIdentifier, AsyncOperation>();
        private readonly object _PendingOperationsLock = new object();

        internal AsyncOperation<T> Register<T>(string name, AsyncOperation<T> operation, bool allowMultipleInvocations)
        {
            return Register(name, null, operation, allowMultipleInvocations);
        }

        internal AsyncOperation<T> Register<T>(string name, object userState, AsyncOperation<T> operation, bool allowMultipleInvocations)
        {
            Register(name, userState, (AsyncOperation) operation, allowMultipleInvocations);
            return operation;
        }

        public bool CanExecute(string name)
        {
            AsyncOperationIdentifier id = new AsyncOperationIdentifier(name, null);

            lock (_PendingOperationsLock)
            {
                return _PendingOperations.ContainsKey(id);
            }
        }


        internal AsyncOperation Register(string name, AsyncOperation operation, bool allowMultipleInvocations)
        {
            return Register(name, null, operation, allowMultipleInvocations);
        }

        internal AsyncOperation Register(string name, object userState, AsyncOperation operation, bool allowMultipleInvocations)
        {
            if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name");
            if (operation == null) throw new ArgumentNullException("operation");

            if (userState == null && allowMultipleInvocations) userState = operation.Id;

            AsyncOperationIdentifier id = new AsyncOperationIdentifier(name, userState);

            lock (_PendingOperationsLock)
            {
                if (_PendingOperations.ContainsKey(id))
                {
                	AsyncOperation.Trace.Verbose("The specified operation has already been registered on this tracker.");
					throw new InvalidOperationException("The specified operation has already been initiated");
                }

                _PendingOperations.Add(id, operation);
            }

			AsyncOperation.Trace.Verbose("Operation {0} has been successfully registered on this tracker.", name);
			return operation;
        }

        internal void Unregister(string name)
        {
            Unregister(name, null, null);
        }

        internal void Unregister(string name, AsyncOperation operation)
        {
            Unregister(name, null, operation);
        }

        internal void Unregister(string name, object userState)
        {
            Unregister(name, userState, null);
        }

        public void Unregister(string name, object userState, AsyncOperation operation)
        {
        	AsyncOperation.Trace.Verbose("Unregistering operation {0} from this tracker.", name);

            lock (_PendingOperationsLock)
            {
                AsyncOperationIdentifier id = new AsyncOperationIdentifier(name, userState);

                if (!_PendingOperations.ContainsKey(id))
                {
                    id = new AsyncOperationIdentifier(name, operation.Id);

                    if (!_PendingOperations.ContainsKey(id)) throw new InvalidOperationException("The specified operation has already been completed.");
                }
                _PendingOperations.Remove(id);
            }
        }

        public void Cancel(string name)
        {
            Cancel(name, null);
        }


        public void Cancel(string name, object userState)
        {
            AsyncOperationIdentifier id = new AsyncOperationIdentifier(name, userState);

            lock (_PendingOperationsLock)
            {
                if (!_PendingOperations.ContainsKey(id)) throw new InvalidOperationException("The specified operation has already been completed.");
                _PendingOperations[id].Cancel();
            }
        }
    }
}